package com.emeraldparser.util;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;

/**
 *  
 * 
 * @author Adam Paynter
 *
 * @param <V>
 * @param <E>
 */
public final class Path<V, E> extends AbstractAutomaton<V, E> {

	private final V firstVertex;
	private final V lastVertex;
	private final ImmutableList<V> vertexList;
	private final ImmutableList<Edge<V, E>> edgeList;

	private Path(V firstVertex, Iterable<? extends Edge<V, E>> edges) {
		this.firstVertex = firstVertex;
		
		ImmutableList.Builder<V> vertexListBuilder = ImmutableList.builder();
		ImmutableList.Builder<Edge<V, E>> edgeListBuilder = ImmutableList.builder();
		
		vertexListBuilder.add(firstVertex);
		
		V previousVertex = firstVertex;
		for (Edge<V, E> edge : edges) {
			if (!Objects.equal(previousVertex, edge.getSource())) {
				throw new IllegalArgumentException("Edges do not form a connected path");
			}
			
			vertexListBuilder.add(edge.getDestination());
			edgeListBuilder.add(edge);
			
			previousVertex = edge.getDestination();
		}
		
		this.lastVertex = previousVertex;
		
		this.vertexList = vertexListBuilder.build();
		this.edgeList = edgeListBuilder.build();
	}

	public static <V, E> Path<V, E> create(V firstVertex, Edge<V, E>... edges) {
		return new Path<V, E>(firstVertex, ImmutableList.copyOf(edges));
	}

	public static <V, E> Path<V, E> create(V firstVertex, Iterable<? extends Edge<V, E>> edges) {
		return new Path<V, E>(firstVertex, edges);
	}

	public boolean isEmpty() {
		return edgeList.isEmpty();
	}

	public int size() {
		return edgeList.size();
	}

	public V getFirstVertex() {
		return firstVertex;
	}

	public V getLastVertex() {
		return lastVertex;
	}

	public Edge<V, E> getFirstEdge() {
		return edgeList.get(0);
	}

	public Edge<V, E> getLastEdge() {
		return edgeList.get(edgeList.size() - 1);
	}

	public E getFirstEdgeLabel() {
		return getFirstEdge().getLabel();
	}

	public E getLastEdgeLabel() {
		return getLastEdge().getLabel();
	}

	public Path<V, E> from(int index) {
		Iterator<? extends Edge<V, E>> iter = edgeList.iterator();
		
		V newFirst = firstVertex;
		for (int i = 0; i < index; i++) {
			newFirst = iter.next().getDestination();
		}
		
		List<Edge<V, E>> edgeSubList = edgeList.subList(index, edgeList.size());
		
		return new Path<V, E>( newFirst, edgeSubList );
	}

	public Path<V, E> to(int index) {
		return new Path<V, E>(firstVertex, edgeList.subList(0, index));
	}

	@Override
	public Collection<V> getStartVertices() {
		return ImmutableSet.of(firstVertex);
	}

	@Override
	public Collection<V> getAcceptVertices() {
		return ImmutableSet.of(lastVertex);
	}

	@Override
	public Collection<V> getRejectVertices() {
		return ImmutableSet.of();
	}

	public Path<V, E> appendPath(Path<V, E> path) {
		if (!Objects.equal(getLastVertex(), path.getFirstVertex())) {
			throw new IllegalArgumentException("Cannot append a path that does not come from the path's last vertex");
		}

		List<Edge<V, E>> newEdges = Lists.newArrayListWithCapacity(edgeList.size() + path.size());
		for (Edge<V, E> oldEdge : edgeList) {
			newEdges.add(oldEdge);
		}
		newEdges.addAll(path.edges());
		
		return new Path<V, E>(firstVertex, newEdges);
	}

	public Path<V, E> appendEdge(Edge<V, E> edge) {
		if (!edge.comesFrom(lastVertex)) {
			throw new IllegalArgumentException("Cannot append an edge that does not come from the path's last vertex");
		}
		
		List<Edge<V, E>> newEdges = Lists.newArrayListWithCapacity(edgeList.size());
		for (Edge<V, E> oldEdge : edgeList) {
			newEdges.add(oldEdge);
		}
		newEdges.add(edge);
		return new Path<V, E>(firstVertex, newEdges);
	}

	@Override
	public List<V> vertices() {
		return vertexList;
	}

	@Override
	public List<Edge<V, E>> edges() {
		return edgeList;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		
		builder.append("Path[").append(firstVertex);
		
		for (Edge<V, E> edge : edgeList) {
			builder.append("->").append(edge.getLabel()).append("->").append(edge.getDestination());
		}
		
		builder.append("]");
		
		return builder.toString();
	}

	@Override
	public Collection<Path<V, E>> getPaths() {
		return ImmutableSet.of(this);
	}

}
